<?php

class DnHttpProxy
{
	public $init_url;
	public $url; //const
	
    public $url_parts=array();  //const
    public $headers=array();
    public $header_keys=array();
    
    public $request_method; //POST GET
    public $request_headers;
    public $post_data='';
    public $is_head_send=false;
    public $is_save_content=false;
    public $content='';
    
	public $is_keep_alive=false;
    
    public $real_host;
    public $real_port;
    
    public $current_data='';
    public $is_echo_content=true;
    
    public $socket=null;
    
    public $hooks=array();
    /** */
    public function __construct($url)
    {
		$this->init_url=$url;
        $this->url=$url;
		
        $this->url_parts=parse_url($url);
    }
     public function __destruct()
    {
        foreach($this->hooks as $name =>$handle){
            unset($this->hooks[$name]);
        }
    }
	
	public function makeUri($a)
    {
        $uri=(isset($a['path'])?$a['path']:'').(isset($a['query'])?'?'.$a['query']:'').(isset($a['fragment'])?'#'.$a['fragment']:'');
        return $uri;
    }
	public function makeUrl($parsed) 
	{
		if (!is_array($parsed)) {return false;}
		$uri = isset($parsed['scheme']) ? $parsed['scheme'].':'.((strtolower($parsed['scheme']) == 'mailto') ? '' : '//') : '';
		$uri .= isset($parsed['user']) ? $parsed['user'].(isset($parsed['pass']) ? ':'.$parsed['pass'] : '').'@' : '';
		$uri .= isset($parsed['host']) ? $parsed['host'] : '';
		$uri .= isset($parsed['port']) ? ':'.$parsed['port'] : '';
		if (isset($parsed['path'])) {
			$uri .= (substr($parsed['path'], 0, 1) == '/') ?
				$parsed['path'] : ((!empty($uri) ? '/' : '' ) . $parsed['path']);
		}
		$uri .= isset($parsed['query']) ? '?'.$parsed['query'] : '';
		$uri .= isset($parsed['fragment']) ? '#'.$parsed['fragment'] : '';

		return $uri;
	} 

    /**  */
    protected function getRequestHeader()
    {
        $header=getallheaders();
		$url_parts=parse_url($this->url);
        $header['Host']=$url_parts['host'];
        return $header;
    }
    public function getHeaderString($headers)
    {
		$out=trim($headers[''])."\r\n";
        foreach($headers as $key =>$value){
			if(!$key){continue;}
            $out .= "$key: $value\r\n";
        }
        
        if( !isset($headers['Connection']) ){
		
            $out .= "Connection: Close\r\n";
        }
        $out.="\r\n";
        return $out;
    }
    
    public function bind($handle,$name='')
    {
		
        if(!$name){$name=uniqid();}
        $this->hooks[$name]=$handle;
    }
    public function fireEvent($eventName)
	{
        $func='on'.$eventName;
		
        foreach($this->hooks as $name => $obj){
            if( is_callable(array(&$obj,$func))){
                $flag=call_user_func(array(&$obj,$func),$this);
            }
			if($flag){break;}
        }
    }
    
    public function Go()
    {
		$this->fireEvent('InitRequest');
		$url_parts=parse_url($this->url);
		
        $this->request_method=$_SERVER['REQUEST_METHOD'];
        $this->request_headers=$this->getRequestHeader();
		$this->request_headers['']=$this->request_method.' '.$this->makeUri($url_parts)." HTTP/1.1";
		$this->request_headers['Host']=$url_parts['host'];
        $this->fireEvent('RequestHeader');
		
        if('POST'==$this->request_method){
            $this->post_data=file_get_contents("php://input");
            $this->fireEvent('PostData');
        }
		
		$this->real_host=$this->request_headers['Host'];
		$this->real_port= isset($url_parts['port'])?$this->url_parts['port']:80; 
			
        @$scheme=($url_parts['port']=='443')?'ssl://':'tcp://'; //TODO ssl is not work!
        
        $this->fireEvent('BeforConnect');
		
        $this->socket= @fsockopen($scheme.$this->real_host,$this->real_port, $errno, $errstr, 5);
        
        if (!$this->socket) {
            $this->fireEvent('ConnectError');
            if(!headers_sent()){
				header("404");
				echo "{$scheme}{$this->real_host},{$this->real_port}"."connect error!";
			}
            return false;
        }
////[[[[
		if(isset($this->request_headers['Via'])){
			unset($this->request_headers['Via']);
		}
		if(isset($this->request_headers['X_FORWARDED_FOR'])){
			unset($this->request_headers['X_FORWARDED_FOR']);
		}
		if(isset($this->request_headers['Proxy-Connection'])){
			unset($this->request_headers['Proxy-Connection']);
		}
////]]]]
		//Content-Length: 1374
		//Keep-Alive: 115
		//Connection: keep-alive
		
		
        $req_header_str=$this->getHeaderString($this->request_headers);
        fwrite($this->socket, $req_header_str);
        if($this->request_method=='POST'){
            fwrite($this->socket, $this->post_data);
        }
        fflush ($this->socket);
        set_time_limit(600);
		$is_thrunked=false;
        while (!feof($this->socket) ) {
            $this->current_data=fgets($this->socket, 2048);
            $str=$this->current_data;
            if(!$this->is_head_send && $str!="\r\n"){
				//header
                @list($key,$value)=explode(':',trim($str)); 
				$init_key=$key;
                $key=trim($key);
                $value=trim($value);
                if(empty($value) && 0==strncmp($key,'HTTP/',strlen('HTTP/'))){
                    $this->headers['']=$key;
                }else if(!empty($key)){
					$value=trim(substr($str,strlen($init_key)+1));
                    $this->headers[$key]=$value;
                }
            }else if(!$this->is_head_send && $str=="\r\n"){
				// header complete 
                foreach($this->headers as $key => $value){
                    $this->header_keys[strtolower($key)]=$key;
                }
                
                $this->fireEvent('Header');
				if($this->headers['Transfer-Encoding']=='chunked'){
					$is_chunked=true;
				}
                header($this->headers['']);
				if(!headers_sent()){
					foreach($this->headers as $key => $value){
						if(!$key){continue;}
						header("$key: $value");
						
					}
				}
                $this->is_head_send=true;
				if($is_chunked){
					//echo "is_chunked!";flush();
				}
                flush();
                if(empty($this->socket)){
                    break;
                }
            }else{
				//save data;
                $this->fireEvent('Data');
                if($this->is_save_content){
                    $this->content.=$this->current_data;
                }
                if($this->is_echo_content){
					//TODO IE 下面 Transfer-Encoding: chunked 的处理
                    echo $this->current_data;
					if(!$is_thunked){
						flush();
					}
                }
                
            }
        }
        $this->fireEvent('Complete');
        if(!empty($this->socket)){
            fclose($this->socket);
        }
        
        return true;
    }
}